﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using Library.Collections.Generic;
using INDEX = System.UInt64;

namespace Library.Linq
{
    internal class RangeEnumerable :
        IOrderedEnumerable<INDEX>,
        IVectorReadOnly<INDEX>,
        IDistinct<INDEX>,
        IFirst<INDEX>,
        IElementAt<INDEX>,
        ILast<INDEX>,
        IMaximum<INDEX>,
        IMinimum<INDEX>,
        IAverage<INDEX>,
        ISkip<INDEX>,
        ITake<INDEX>
    {
        struct Enumerator : IEnumerator<INDEX>
        {
            public Enumerator(INDEX start, INDEX count)
            {
                this.started = false;
                this.start = start;
                this.current = start;
                this.count = count;
            }

            bool started;
            INDEX start;
            INDEX current;
            INDEX count;

            public INDEX Current
            {
                get
                {
                    if (!started) throw new System.InvalidOperationException();
                    return current;
                }
            }

            public void Dispose()
            {
                
            }

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    if (!started) throw new System.InvalidOperationException();
                    return current;
                }
            }

            public bool MoveNext()
            {
                if (!started)
                {
                    started = true;
                    return count != 0;
                }
                return ++current < count;
            }

            public void Reset()
            {
                current = start;
                started = false;
            }
        }

        public RangeEnumerable(INDEX start, INDEX count)
        {
            if (count < 1) throw new ArgumentOutOfRangeException();
            this.start = start;
            this.count = count;
        }

        INDEX start;
        INDEX count;

        public INDEX this[INDEX key]
        {
            get
            {
                if (key < count)
                    return key + start;
                else
                    throw new IndexOutOfRangeException();
            }
        }
        public bool TryGetValue(INDEX key, out INDEX value)
        {
            if (key < count)
            {
                value = key + start;
                return true;
            }
            else
            {
                value = default(INDEX);
                return false;
            }
        }

        public bool Contains(INDEX item)
        {
            return start <= item && item < start + count;
        }
        
        public INDEX Count
        {
            get
            {
                return count;
            }
        }

        public IEnumerator<INDEX> GetEnumerator()
        {
            return new Enumerator(start, count);
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        public IEnumerator<INDEX> GetEnumerator(Direction direction)
        {
            throw new NotImplementedException();
        }
        IEnumerator Collections.Specialized.IEnumerableDirection.GetEnumerator(Direction direction)
        {
            throw new NotImplementedException();
        }

        public bool Contains(INDEX item, out INDEX index)
        {
            if (start <= item && item < start + count)
            {
                index = item - start;
                return true;
            }
            else
            {
                index = 0;
                return false;
            }
        }

        public IEnumerable<INDEX> Input
        {
            get
            {
                return null;
            }
        }
        IEnumerable IOrderedEnumerable.Input
        {
            get
            {
                return null;
            }
        }

        static ParameterExpression x = Expression.Variable(typeof(INDEX));
        static ParameterExpression y = Expression.Variable(typeof(INDEX));
        static LambdaExpression keySelector = Expression.Lambda<Func<INDEX, INDEX>>(x, x);
        public LambdaExpression KeySelector
        {
            get
            {
                return keySelector;
            }
        }

        public IComparer Comparer
        {
            get
            {
                return null; //Comparer<INDEX>.Default;
            }
        }

        static Expression<Func<INDEX, INDEX, int>> comparison = Expression.Lambda<Func<INDEX, INDEX, int>>(Expression.Call(x, typeof(INDEX).GetMethod("CompareTo", new Type[] { typeof(INDEX) }), y), x, y);
        public Expression<Func<INDEX, INDEX, int>> Comparison
        {
            get
            {
                return comparison;
            }
        }
        LambdaExpression IOrderedEnumerable.Comparison
        {
            get
            {
                return comparison;
            }
        }

        public bool IsDistinct(out bool value)
        {
            value = true;
            return true;
        }
        public IEnumerable<INDEX> Distinct()
        {
            return this;
        }
        public IEnumerable<INDEX> Distinct(IEqualityComparer<INDEX> comparer)
        {
            if (comparer == null) throw new ArgumentNullException("comparer");

            if (comparer == EqualityComparer<INDEX>.Default)
                return this;

            return System.Linq.Enumerable.Distinct(this, comparer);
        }

        public bool First(out INDEX item)
        {
            item = start;
            return true;
        }

        public bool ElementAt(INDEX index, out INDEX item)
        {
            if (index < count)
            {
                item = index + start;
                return true;
            }
            else
            {
                item = 0;
                return false;
            }
        }

        public bool Last(out INDEX item)
        {
            item = start + count - 1;
            return true;
        }

        public bool Maximum(out INDEX item)
        {
            item = start + count - 1;
            return true;
        }

        public bool Minimum(out INDEX item)
        {
            item = start;
            return true;
        }

        public bool Average(out INDEX item)
        {
            item = (start + count - 1) / 2;
            return true;
        }

        public IEnumerable<INDEX> Skip(INDEX count)
        {
            ulong n = this.count - count;
            if (n == 0) return Library.Linq.Enumerable.Empty<INDEX>();
            return new RangeEnumerable(start + count, n);
        }

        public IEnumerable<INDEX> Take(INDEX count)
        {
            if (count < this.count)
            {
                return new RangeEnumerable(start, count);
            }
            return this;
        }

        public object Clone(Direction direction, Access access)
        {
            throw new NotImplementedException();
        }

        public object Clone(Direction direction)
        {
            throw new NotImplementedException();
        }

        public object Clone()
        {
            return new RangeEnumerable(start, count);
        }

        public object Clone(Access access)
        {
            throw new NotImplementedException();
        }
    }
}
